﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System.Runtime.Serialization;
using System;
using System.Collections;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Xml.Serialization;
#if USE_HYPER
using Hyper.ComponentModel;
#endif

namespace BrainTechLLC.ThreadSafeObjects
{
    /// <summary>
    /// Thread-safe lookup allows multiple threads to look up items by key, add items,
    /// and remove items in a thread-safe manner
    /// </summary>
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
    [EditorBrowsable(EditorBrowsableState.Always)]
#endif
    [Serializable]
    [DataContract]
    public class ThreadSafeLookup<TIndex, TType> : Lockable, IDictionary<TIndex, TType>, IMultipleItems<TType>,
        ISupportsCount,
        INotifyCollectionChanged,
        ISupportsAddRemoveClear<TIndex, TType> where TType : class
    {
        public override string ToString()
        {
            return Count.ToString();
        }

        [DataMember]
        public Dictionary<TIndex, TType> Lookup;


        /// <summary>
        /// Returns an array containing all items in the collection
        /// </summary>
        [XmlIgnore]
        public TType[] ArrayOfItems { get { return AllItems.ToArray(); } }

        [XmlIgnore]
        public int Count { get { return Lookup.Count; } }

        public bool ContainsKey(TIndex key) { return Lookup.ContainsKey(key); }
        public bool TryGetValue(TIndex key, out TType items) { return Lookup.TryGetValue(key, out items); }

        /// <summary>
        /// Merges two thread safe lookups
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="other"></param>
        /// <returns></returns>
        public ThreadSafeLookup<TIndex, TType> Merge<T>(ThreadSafeLookup<TIndex, T> other) where T : class, TType
        {
            var kvps = other.AllKeyValuePairs;
            AquireLock();
            foreach (var kvp in kvps)
            {
                if (!Lookup.ContainsKey(kvp.Key))
                {
                    Lookup.Add(kvp.Key, kvp.Value);
                }
            }
            ReleaseLock();

            return this;
        }

        public List<TType> GetAllItemsAndClear()
        {
            List<TType> result = new List<TType>();
            AquireLock();
            Lookup.ForEach(kvp => result.Add(kvp.Value));
            Lookup.Clear();
            ReleaseLock();

            return result;
        }

        /// <summary>
        /// Performs action on each item (locks before iteration, unlocks after)
        /// </summary>
        /// <param name="action"></param>
        public void ForEachLocked(Action<TIndex, TType> action)
        {
            AquireLock();
            {
                var kvps = AllKeyValuePairs;
                foreach (var kvp in kvps)
                {
                    action(kvp.Key, kvp.Value);
                }
            }
            ReleaseLock();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Lookup.GetEnumerator();
        }

        public IEnumerator<KeyValuePair<TIndex, TType>> GetEnumerator()
        {
            return Lookup.GetEnumerator();
        }

        [XmlIgnore]
        public bool IsReadOnly { get { return false; } }

        [XmlIgnore]
        public List<TIndex> Keys
        {
            get
            {
                List<TIndex> items = new List<TIndex>(Lookup.Count);
                AquireLock(); { Lookup.ForEach(kvp => items.Add(kvp.Key)); } ReleaseLock();
                return items;
            }
        }

        public ThreadSafeLookup() { Lookup = new Dictionary<TIndex, TType>(); }
        public ThreadSafeLookup(StringComparer comparer) { Lookup = new Dictionary<TIndex, TType>((IEqualityComparer<TIndex>)comparer); }
        public ThreadSafeLookup(StringComparer comparer, params KeyValuePair<TIndex, TType>[] items)
        {
            Lookup = new Dictionary<TIndex, TType>((IEqualityComparer<TIndex>)comparer);

            foreach (var kvp in items)
                Add(kvp.Key, kvp.Value);
        }

        /// <summary>
        /// Returns all key/value pairs in the lookup
        /// </summary>
        [XmlIgnore]
        public List<KeyValuePair<TIndex, TType>> AllKeyValuePairs
        {
            get
            {
                List<KeyValuePair<TIndex, TType>> results = new List<KeyValuePair<TIndex, TType>>();
                AquireLock();
                {
                    foreach (var v in Lookup)
                        results.Add(v);
                }
                ReleaseLock();

                return results;
            }
        }

        /// <summary>
        /// Returns a copy of all items in the lookup
        /// </summary>
        [XmlIgnore]
        public List<TType> AllItems
        {
            get
            {
                // TODO: caching
                List<TType> items = new List<TType>(Lookup.Count);
                AquireLock(); { Lookup.ForEach(kvp => items.Add(kvp.Value)); } ReleaseLock();
                return items;
            }
        }

        /// <summary>
        /// Adds an item to the lookup
        /// </summary>
        /// <param name="key"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Add(TIndex key, TType item)
        {
            bool added = false;

            AquireLock();
            {
                if (!Lookup.ContainsKey(key))
                {
                    Lookup.Add(key, item);
                    added = true;
                }
            }
            ReleaseLock();

            if (added)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);

            return added;
        }

        /// <summary>
        /// Adds an item under multiple keys
        /// </summary>
        /// <param name="keys"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool AddMultipleKeys(IEnumerable<TIndex> keys, TType item)
        {
            bool added = false;

            AquireLock();
            {
                foreach (TIndex key in keys)
                {
                    if (!Lookup.ContainsKey(key))
                    {
                        Lookup.Add(key, item);
                        added = true;
                    }
                }
            }
            ReleaseLock();

            if (added)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);

            return added;
        }

        /// <summary>
        /// Adds a value if it does not exist, otherwise sets the value having the specified key to 'item'
        /// </summary>
        /// <param name="key"></param>
        /// <param name="item"></param>
        public void AddOrSet(TIndex key, TType item)
        {
            if (Lookup.ContainsKey(key))
            {
                TType old = null;
                Lookup.TryGetValue(key, out old);
                if (!old.AreEqual(item))
                {
                    Lookup[key] = item;

                    if (CollectionChanged != null)
                        OnNotifyCollectionChanged(NotifyCollectionChangedAction.Replace, new List<TType>() { old, item });
                }
            }
            else
            {
                Add(key, item);
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
            }
        }

        /// <summary>
        /// If the item doesn't exist, run func(), add its return value
        /// </summary>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public bool AddIfNew(TIndex key, Func<TType> func)
        {
            if (!Lookup.ContainsKey(key))
                return Add(key, func());

            return false;
        }

        /// <summary>
        /// Gets the value with the corresponding key in the collection, or returns null if not found
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        [XmlIgnore]
        public TType this[TIndex key]
        {
            get
            {
                TType item;

                // Not totally safe for lookups that frequently add/remove items.
                // For thread safety, surround the following line with AquireLock(); / ReleaseLock();
                if (!Lookup.TryGetValue(key, out item))
                {
                    item = null;
                }

                return item;
            }
            set
            {
                AddOrSet(key, value);
            }
        }

        /// <summary>
        /// Removes an item by key
        /// </summary>
        /// <param name="key"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Remove(TIndex key, TType item)
        {
            return Remove(key);
        }

        /// <summary>
        /// Removes an item by key
        /// </summary>
        /// <param name="kvp"></param>
        /// <returns></returns>
        public bool Remove(KeyValuePair<TIndex, TType> kvp)
        {
            return Remove(kvp.Key, kvp.Value);
        }

        /// <summary>
        /// Removes an item by key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Remove(TIndex key)
        {
            bool removed = false;

            if (!Lookup.ContainsKey(key))
                return false;

            TType item = null;
            AquireLock();
            {
                if (Lookup.TryGetValue(key, out item))
                {
                    Lookup.Remove(key);
                    removed = true;
                }
            }
            ReleaseLock();

            if (item != null)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);
            return removed;
        }

        public void Clear()
        {
            IList<TType> items = null;

            if (CollectionChanged != null)
                items = new List<TType>(AllItems);

            AquireLock();
            {
                Lookup.Clear();
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset, items);
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, IList<TType> changedItems)
        {
#if SILVERLIGHT
#else
            if (CollectionChanged != null)
            {
                switch (action)
                {
                    case NotifyCollectionChangedAction.Add:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TType>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TType>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Replace:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TType>(action, changedItems[0], changedItems[1]));
                        break;
                    case NotifyCollectionChangedAction.Reset:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TType>(action));
                        break;
                }
            }
#endif
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, TType changedItem)
        {
            if (CollectionChanged != null)
                OnNotifyCollectionChanged(action, new List<TType>() { changedItem });
        }

        #region INotifyCollectionChanged Members

        [field: NonSerialized]
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region IDictionary<TIndex,TListType> Members

        void IDictionary<TIndex, TType>.Add(TIndex key, TType value)
        {
            this.Add(key, value);
        }

#if NO_SILVERLIGHT
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
#endif
        [XmlIgnore]
        ICollection<TIndex> IDictionary<TIndex, TType>.Keys
        {
            get { return Lookup.Keys; }
        }

#if NO_SILVERLIGHT
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
#endif
        [XmlIgnore]
        public ICollection<TType> Values
        {
            get { return Lookup.Values; }
        }

        #endregion

        #region ICollection<KeyValuePair<TIndex,TListType>> Members

        public void Add(KeyValuePair<TIndex, TType> item)
        {
            Add(item.Key, item.Value);
        }

        public bool Contains(KeyValuePair<TIndex, TType> item)
        {
            return Lookup.Contains(item);
        }

        #endregion

        #region ICollection<KeyValuePair<TIndex,TListType>> Members

        public void CopyTo(KeyValuePair<TIndex, TType>[] array, int arrayIndex)
        {
            AquireLock();
            {
                foreach (KeyValuePair<TIndex, TType> item in Lookup)
                {
                    array[arrayIndex++] = item;
                }
            }
            ReleaseLock();
        }

        #endregion
    }

    /// <summary>
    /// Thread-safe lookup allows multiple threads to look up items by key, add items,
    /// and remove items in a thread-safe manner
    /// </summary>
    /// <typeparam name="TListType"></typeparam>
    /// <typeparam name="TIndex"></typeparam>
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class ThreadSafeLookupNonRef<TIndex, TType> : Lockable, INotifyCollectionChanged,
        IDictionary<TIndex, TType>, IMultipleItems<TType>, ISupportsCount
    {
        public override string ToString()
        {
            return Count.ToString();
        }

        // add track changes
        [NonSerialized, XmlIgnore]
        public bool Modified;

        [DataMember]
        public Dictionary<TIndex, TType> Lookup = new Dictionary<TIndex, TType>();

        /// <summary>
        /// Returns an array containing all items in the collection
        /// </summary>
        [XmlIgnore]
        public TType[] ArrayOfItems { get { return AllItems.ToArray(); } }

        /// <summary>
        /// Returns a mostly-accurate count of items currently in the collection
        /// </summary>
        [XmlIgnore]
        public int Count { get { return Lookup.Count; } }

        public bool ContainsKey(TIndex key) { return Lookup.ContainsKey(key); }

        public bool TryGetValue(TIndex key, out TType items) { return Lookup.TryGetValue(key, out items); }

        public bool Remove(KeyValuePair<TIndex, TType> kvp)
        {
            return Remove(kvp.Key, kvp.Value);
        }

        /// <summary>
        /// Returns a snapshot/list copy of all items in the collection
        /// </summary>
        public List<TType> AllItems
        {
            get
            {
                List<TType> items = new List<TType>(Lookup.Count);

                AquireLock();
                {
                    foreach (KeyValuePair<TIndex, TType> kvp in Lookup)
                    {
                        items.Add(kvp.Value);
                    }
                }
                ReleaseLock();

                return items;
            }
        }

        /// <summary>
        /// Adds an item under multiple keys
        /// </summary>
        /// <param name="keys"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool AddMultipleKeys(IEnumerable<TIndex> keys, TType item)
        {
            bool added = false;

            AquireLock();
            {
                foreach (TIndex key in keys)
                {
                    if (!Lookup.ContainsKey(key))
                    {
                        Lookup.Add(key, item);
                        added = true;
                    }
                }
            }
            ReleaseLock();

            if (added)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);

            return added;
        }

        /// <summary>
        /// Adds multiple items according to their corresponding item keys
        /// </summary>
        /// <param name="keys"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public bool AddMultiple(IEnumerable<TIndex> keys, IList<TType> values)
        {
            List<TType> addedItems = null;
            bool added = false;
            int n = 0;

            if (CollectionChanged != null)
                addedItems = new List<TType>();

            AquireLock();
            {
                foreach (TIndex key in keys)
                {
                    if (!Lookup.ContainsKey(key))
                    {
                        Lookup.Add(key, values[n]);
                        if (addedItems != null) addedItems.Add(values[n]);
                        Modified = true;
                        added = true;
                    }
                    n++;
                }
            }
            ReleaseLock();

            if (added)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, addedItems);

            return added;
        }

        /// <summary>
        /// Adds an item to the collection
        /// </summary>
        /// <returns>True if the item was already in the collection, otherwise false</returns>        
        public bool Add(TIndex key, TType item)
        {
            bool found = true;

            AquireLock();
            {
                if (!Lookup.ContainsKey(key))
                {
                    Lookup.Add(key, item);
                    Modified = true;
                    found = false;
                }
            }
            ReleaseLock();

            if (!found)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);

            return !found;
        }

        /// <summary>
        /// If the item doesn't exist, run func(), add its return value
        /// </summary>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public bool AddIfNew(TIndex key, Func<TType> func)
        {
            if (!Lookup.ContainsKey(key))
                return Add(key, func());

            return false;
        }

        /// <summary>
        /// Adds a value if it does not exist, otherwise sets the value having the specified key to 'item'
        /// </summary>
        /// <param name="key"></param>
        /// <param name="item"></param>
        public void AddOrSet(TIndex key, TType item)
        {
            if (Lookup.ContainsKey(key))
            {
                TType old = default(TType);
                Lookup.TryGetValue(key, out old);
                if (!old.Equals(item))
                {
                    Lookup[key] = item;
                    Modified = true;

                    if (CollectionChanged != null)
                        OnNotifyCollectionChanged(NotifyCollectionChangedAction.Replace, new List<TType>() { old, item });
                }
            }
            else
            {
                Add(key, item);
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
            }
        }

        /// <summary>
        /// Gets the value with the corresponding key in the collection, or returns default(TListType)
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public TType this[TIndex key]
        {
            get
            {
                TType item;

                // Not totally safe for lookups that frequently add/remove items.
                // For thread safety, surround the following line with AquireLock(); / ReleaseLock();
                if (!Lookup.TryGetValue(key, out item)) item = default(TType);

                return item;
            }
            set
            {
                AddOrSet(key, value);
            }
        }

        /// <summary>
        /// Removes an item by key
        /// </summary>
        /// <param name="key"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Remove(TIndex key, TType item)
        {
            return Remove(key);
        }

        /// <summary>
        /// Removes an item by key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Remove(TIndex key)
        {
            bool removed = false;

            if (!Lookup.ContainsKey(key))
                return false;

            TType item = default(TType);
            AquireLock();
            {
                if (Lookup.TryGetValue(key, out item))
                {
                    Lookup.Remove(key);
                    Modified = true;
                    removed = true;
                }
            }
            ReleaseLock();

            if (removed)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);

            return removed;
        }

        /// <summary>
        /// Clears all items from the collection
        /// </summary>
        public void Clear()
        {
            IList<TType> items = null;

            if (CollectionChanged != null)
                items = new List<TType>(AllItems);

            AquireLock();
            {
                Lookup.Clear();
                Modified = true;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset, items);
        }

        /// <summary>
        /// Merges two thread safe lookups
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="other"></param>
        /// <returns></returns>
        public ThreadSafeLookupNonRef<TIndex, TType> Merge<T>(ThreadSafeLookupNonRef<TIndex, T> other) where T : class, TType
        {
            var kvps = other.AllKeyValuePairs;
            AquireLock();
            foreach (var kvp in kvps)
            {
                if (!Lookup.ContainsKey(kvp.Key))
                {
                    Lookup.Add(kvp.Key, kvp.Value);
                }
            }
            ReleaseLock();

            return this;
        }

        /// <summary>
        /// Performs action on each item (locks before iteration, unlocks after)
        /// </summary>
        /// <param name="action"></param>
        public void ForEachLocked(Action<TIndex, TType> action)
        {
            AquireLock();
            {
                var kvps = AllKeyValuePairs;
                foreach (var kvp in kvps)
                {
                    action(kvp.Key, kvp.Value);
                }
            }
            ReleaseLock();
        }

        /// <summary>
        /// Returns all key/value pairs in the lookup
        /// </summary>
        [XmlIgnore]
        public List<KeyValuePair<TIndex, TType>> AllKeyValuePairs
        {
            get
            {
                List<KeyValuePair<TIndex, TType>> results = new List<KeyValuePair<TIndex, TType>>();
                AquireLock();
                {
                    foreach (var v in Lookup)
                        results.Add(v);
                }
                ReleaseLock();

                return results;
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Lookup.GetEnumerator();
        }

        public IEnumerator<KeyValuePair<TIndex, TType>> GetEnumerator()
        {
            return Lookup.GetEnumerator();
        }

        public bool IsReadOnly { get { return false; } }

        /// <summary>
        /// Returns all keys for items in the collection
        /// </summary>
        public List<TIndex> Keys
        {
            get
            {
                List<TIndex> items = new List<TIndex>(Lookup.Count);
                AquireLock(); { Lookup.ForEach(kvp => items.Add(kvp.Key)); } ReleaseLock();
                return items;
            }
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, IList<TType> changedItems)
        {
#if SILVERLIGHT
#else
            if (CollectionChanged != null)
            {
                switch (action)
                {
                    case NotifyCollectionChangedAction.Add:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TType>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TType>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Replace:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TType>(action, changedItems[0], changedItems[1]));
                        break;
                    case NotifyCollectionChangedAction.Reset:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TType>(action));
                        break;
                }
            }
#endif
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, TType changedItem)
        {
            if (CollectionChanged != null)
                OnNotifyCollectionChanged(action, new List<TType>() { changedItem });
        }

        #region INotifyCollectionChanged Members

        [field: NonSerialized]
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region IDictionary<TIndex,TListType> Members

        void IDictionary<TIndex, TType>.Add(TIndex key, TType value)
        {
            this.Add(key, value);
        }

        ICollection<TIndex> IDictionary<TIndex, TType>.Keys
        {
            get { return Lookup.Keys; }
        }

        public ICollection<TType> Values
        {
            get { return Lookup.Values; }
        }

        #endregion

        #region ICollection<KeyValuePair<TIndex,TListType>> Members

        /// <summary>
        /// Adds an item to the lookup
        /// </summary>
        /// <param name="item"></param>
        public void Add(KeyValuePair<TIndex, TType> item)
        {
            Add(item.Key, item.Value);
        }

        public bool Contains(KeyValuePair<TIndex, TType> item)
        {
            return Lookup.Contains(item);
        }

        #endregion

        #region ICollection<KeyValuePair<TIndex,TListType>> Members

        public void CopyTo(KeyValuePair<TIndex, TType>[] array, int arrayIndex)
        {
            AquireLock();
            {
                foreach (KeyValuePair<TIndex, TType> item in Lookup)
                {
                    array[arrayIndex++] = item;
                }
            }
            ReleaseLock();
        }

        #endregion

        public int Increment(TIndex index, int amount)
        {
            int value = 0;
            AquireLock();
            {
                value = Convert.ToInt32(this[index]);
                value += amount;
                Lookup[index] = (TType)Convert.ChangeType(value, typeof(TType));
                Modified = true;
            }
            ReleaseLock();
            return value;
        }
    }

    /// <summary>
    /// Thread-safe lookup allows multiple threads to look up items by key, add items,
    /// and remove items in a thread-safe manner
    /// </summary>
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class ThreadSafeLookupOnAutoKey<TIndex, TListType> : ThreadSafeLookup<TIndex, TListType>, IMultipleItems<TListType>,
        ISupportsCount, ISupportsAddRemoveClear<TIndex, TListType> where TListType : class, IKeyedOnProperty<TIndex>
    {
        public override string ToString()
        {
            return Count.ToString();
        }

        public bool Add(TListType item)
        {
            TIndex key = item.Key;
            return Add(key, item);
        }

        public void AddOrSet(TListType item)
        {
            TIndex key = item.Key;
            AddOrSet(key, item);
        }

        public bool Remove(TListType item)
        {
            TIndex key = item.Key;
            return Remove(item.Key);
        }
    }

    /// <summary>
    /// Thread-safe lookup, creating/populating values if not found in the lookup
    /// </summary>
    /// <typeparam name="T1"></typeparam>
    /// <typeparam name="T2"></typeparam>
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class ThreadSafeLookupOnDemand<T1, T2> : ThreadSafeLookup<T1, T2> where T2 : class, new()
    {
        protected Cached<List<T2>> _cachedList;

        public Cached<List<T2>> CachedList
        {
            get
            {
                return Populate.OnDemand<Cached<List<T2>>>(ref _cachedList, () => new Cached<List<T2>>(
                     delegate(object param, out List<T2> result, object[] args) { result = base.AllItems; return true; }));
            }
        }

        public void MarkDirty()
        {
            if (_cachedList != null)
                _cachedList.MarkDirty();
        }

        public new List<T2> AllItems { get { return CachedList.GetItem(); } }

        public bool Contains(T1 key)
        {
            return ContainsKey(key);
        }

        /// <summary>
        /// Gets an item, or if not found in the collection, creates a new item and adds it to the collection
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public T2 Get(T1 key)
        {
            T2 item = this[key];

            if (item == null)
            {
                item = new T2();
                AddOrSet(key, item);
            }

            return item;
        }

        public new void AddOrSet(T1 key, T2 item)
        {
            base.AddOrSet(key, item);
            MarkDirty();
        }

        /// <summary>
        /// Gets an item, or if not found in the collection, creates a new item and adds it to the collection
        /// </summary>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public T2 Get(T1 key, Func<T1, T2> func)
        {
            T2 item = this[key];

            if (item == null)
            {
                item = func(key);
                base.AddOrSet(key, item);

                if (_cachedList != null)
                    _cachedList.MarkDirty();
            }

            return item;
        }
    }

    /// <summary>
    /// Thread-safe lookup, creating/populating values if not found in the lookup
    /// </summary>
    /// <typeparam name="T1"></typeparam>
    /// <typeparam name="T2"></typeparam>
    [Serializable]
    [DataContract]
    public class ThreadSafeLookupOnDemandWithDelegate<T1, T2> : ThreadSafeLookup<T1, T2> where T2 : class
    {
        [NonSerialized, XmlIgnore]
        protected Cached<List<T2>> _cachedList;

        public ThreadSafeLookupOnDemandWithDelegate() { }
        public ThreadSafeLookupOnDemandWithDelegate(StringComparer comparer)
            : base(comparer)
        {
        }

        public Cached<List<T2>> CachedList
        {
            get
            {
                return Populate.OnDemand<Cached<List<T2>>>(ref _cachedList, () => new Cached<List<T2>>(
                     delegate(object param, out List<T2> result, object[] args) { result = base.AllItems; return true; }));
            }
        }

        public void MarkDirty()
        {
            if (_cachedList != null)
                _cachedList.MarkDirty();
        }

        public new List<T2> AllItems { get { return CachedList.GetItem(); } }

        public new void AddOrSet(T1 key, T2 item)
        {
            base.AddOrSet(key, item);

            MarkDirty();
        }

        /// <summary>
        /// Gets an item, or if not found in the collection, creates a new item and adds it to the collection
        /// </summary>
        /// <param name="key"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public T2 Get(T1 key, Func<T1, T2> func)
        {
            T2 item = this[key];

            if (item == null)
            {
                item = func(key);
                base.AddOrSet(key, item);

                if (_cachedList != null)
                    _cachedList.MarkDirty();
            }

            return item;
        }
    }

    /// <summary>
    /// A thread safe list & lookup allows multiple threads to efficiently work
    /// with both a single list for Adding, Removing, and Searching for lists of items by key
    /// This version is safe for collections that frequently remove/add items
    /// </summary>
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class SafeThreadSafeListAndLookup<TIndex, TListType> :
       Lockable, IMultipleItems<TListType>,
       ISupportsFindMany<TIndex, TListType>,
       ISupportsCount,
       IDictionary<TIndex, List<TListType>>,
       ISupportsAddRemoveClear<TIndex, TListType> where TListType : class
    {
        /// <summary>
        /// Stores a running count of items
        /// </summary>
        [DataMember]
        protected int _count;

        /// <summary>
        /// A full list of items in the collection
        /// </summary>
        [DataMember]
        protected List<TListType> _list = new List<TListType>();

        /// <summary>
        /// A dictionary of list of items, use for fast lookup by index/key
        /// </summary>
        [DataMember]
        protected Dictionary<TIndex, List<TListType>> Lookup = new Dictionary<TIndex, List<TListType>>();

        /// <summary>
        /// Stores a common (cached) empty list
        /// </summary>
#if NO_SILVERLIGHT
        [NonSerialized, XmlIgnore]
#endif
        protected readonly static List<TListType> _emptyList = new List<TListType>();

        public object Clone() { return MakeCopy(); }

        protected ThreadSafeListAndLookup<TIndex, TListType> MakeCopy() { throw new NotImplementedException(); }

        /// <summary>
        /// Returns an array containing all items in the collection
        /// </summary>
        public TListType[] ArrayOfItems { get { return AllItems.ToArray(); } }

        /// <summary>
        /// Returns a mostly-accurate count of items currently in the collection
        /// </summary>
        public int Count { get { return _count; } }

        /// <summary>
        /// Adds an item to the collection
        /// </summary>
        /// <returns>True if the item was already in the collection, otherwise false</returns>
        public virtual bool Add(TIndex key, TListType item)
        {
            List<TListType> list;
            bool found = true;

            AquireLock();
            {
                if (!Lookup.TryGetValue(key, out list))
                {
                    list = new List<TListType>();
                    Lookup.Add(key, list);
                    found = false;
                }

                if (!list.Contains(item))
                {
                    list.Add(item);
                    _list.Add(item);
                    _count++;
                    found = false;
                }
            }
            ReleaseLock();

            return found;
        }

        /// <summary>
        /// Returns a snapshot/list copy of all items in the collection
        /// </summary>
        public List<TListType> AllItems
        {
            get
            {
                List<TListType> list = new List<TListType>(_count);

                AquireLock();
                {
                    list.AddRange(_list);
                }
                ReleaseLock();

                return list;
            }
        }

        /// <summary>
        /// Finds the first item in the collection with the given key
        /// </summary>
        public virtual TListType FindSingle(TIndex key)
        {
            TListType item = null;
            List<TListType> list;

            AquireLock();
            {
                if (Lookup.TryGetValue(key, out list))
                {
                    if (list.Count > 0)
                    {
                        item = list[0];
                    }
                }
            }
            ReleaseLock();

            return item;
        }

        /// <summary>
        /// Removes an item from the collection
        /// </summary>
        /// <returns>True if the item was removed, false if the item could not be found in the collection</returns>
        public bool Remove(TIndex key, TListType item)
        {
            if (!Lookup.ContainsKey(key))
                return false;

            List<TListType> list;

            AquireLock();
            {
                if (Lookup.TryGetValue(key, out list))
                {
                    list.Remove(item);
                }
                if (_list.Remove(item))
                {
                    _count--;
                }
            }
            ReleaseLock();

            return true;
        }

        /// <summary>
        /// Removes a single item from the collection based on a key and a match delegate
        /// </summary>
        public TListType Remove(TIndex key, Func<TListType, bool> match)
        {
            if (!Lookup.ContainsKey(key))
                return null;

            List<TListType> list;
            TListType found = null;

            AquireLock();
            {
                if (Lookup.TryGetValue(key, out list))
                {
                    for (int n = 0; n < list.Count; n++)
                    {
                        TListType item = list[n];
                        if (match(item))
                        {
                            list.RemoveAt(n);
                            break;
                        }
                    }
                }

                for (int n = 0; n < _list.Count; n++)
                {
                    TListType item = _list[n];
                    if (match(item))
                    {
                        found = item;
                        _list.RemoveAt(n);
                        _count--;
                        break;
                    }
                }
            }
            ReleaseLock();

            return found;
        }

        /// <summary>
        /// Clears all items from the collection
        /// </summary>
        public void Clear()
        {
            AquireLock();
            {
                _list.Clear();
                Lookup.Clear();
                _count = 0;
            }
            ReleaseLock();
        }

        /// <summary>
        /// Finds all items in the collection with the given key
        /// </summary>
        public List<TListType> Find(TIndex key)
        {
            List<TListType> list;
            if (!Lookup.ContainsKey(key))
            {
                return _emptyList;
            }
            else
            {
                AquireLock();
                {
                    if (Lookup.TryGetValue(key, out list))
                    {
                        list = new List<TListType>(list);
                    }
                    else
                    {
                        list = _emptyList;
                    }
                }
                ReleaseLock();

                return list;
            }
        }

        /// <summary>
        /// Finds a single item in the collection with the given key, using a match delegate
        /// </summary>
        public TListType Find(TIndex key, Func<TListType, bool> match)
        {
            List<TListType> list;
            if (!Lookup.ContainsKey(key))
                return null;

            TListType item = null;

            AquireLock();
            {
                if (Lookup.TryGetValue(key, out list))
                {
                    for (int n = 0; n < list.Count; n++)
                    {
                        TListType find = list[n];
                        if (match(find))
                        {
                            item = find;
                            break;
                        }
                    }
                }
                ReleaseLock();

                return item;
            }
        }

        #region IDictionary<TIndex,List<TListType>> Members

        public void Add(TIndex key, List<TListType> items)
        {
            foreach (TListType o in items)
                Add(key, o);
        }

        public bool ContainsKey(TIndex key)
        {
            return Lookup.ContainsKey(key);
        }

        public ICollection<TIndex> Keys
        {
            get { return Lookup.Keys; }
        }

        public bool Remove(TIndex key)
        {
            return this.Remove(key);
        }

        public bool TryGetValue(TIndex key, out List<TListType> value)
        {
            return Lookup.TryGetValue(key, out value);
        }

        public ICollection<List<TListType>> Values
        {
            get { return Lookup.Values; }
        }

        public List<TListType> this[TIndex key]
        {
            get
            {
                return Find(key);
            }
            set
            {
                // TODO - remove existing?
                Add(key, value);
            }
        }

        #endregion

        #region ICollection<KeyValuePair<TIndex,List<TListType>>> Members

        public void Add(KeyValuePair<TIndex, List<TListType>> item)
        {
            foreach (TListType o in item.Value)
                Add(item.Key, o);
        }

        public bool Contains(KeyValuePair<TIndex, List<TListType>> item)
        {
            return Lookup.ContainsKey(item.Key);
        }

        public void CopyTo(KeyValuePair<TIndex, List<TListType>>[] array, int arrayIndex)
        {
            AquireLock();
            {
                foreach (KeyValuePair<TIndex, List<TListType>> item in Lookup)
                {
                    array[arrayIndex++] = item;
                }
            }
            ReleaseLock();
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(KeyValuePair<TIndex, List<TListType>> item)
        {
            return Remove(item.Key);
        }

        #endregion

        #region IEnumerable<KeyValuePair<TIndex,List<TListType>>> Members

        public IEnumerator<KeyValuePair<TIndex, List<TListType>>> GetEnumerator()
        {
            return Lookup.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Lookup.GetEnumerator();
        }

        #endregion
    }
}